#ifndef GAMEENGINE_GEOMETRY_HEXAGON2D_HPP
#define GAMEENGINE_GEOMETRY_HEXAGON2D_HPP

#ifdef GAMEENGINE_NAMESPACES
namespace GameEngine {
#endif

//=================================================================================
// Implementation of the Hexagon2d class

//! Default constructor
/*!
 @note Class T should have a default constructor
 */
template <class T>
Hexagon2d<T>::Hexagon2d() {}

//! Constructor from a center and an orientation vector
/*!
 @param center The center of the hexagon
 @param orientation The orientation vector of the hexagon
 @note Class T should have a copy constructor
 */
template <class T>
Hexagon2d<T>::Hexagon2d(const Point2d<T>& center, const Vector2d<T>& orientation) :
	center_(center), orientation_(orientation) {}

//! Copy constructor
/*!
 @param rhs The hexagon to copy
 @note Class T should have a copy constructor
 */
template <class T>
Hexagon2d<T>::Hexagon2d(const Hexagon2d& rhs) :
	center_(rhs.center_), orientation_(rhs.orientation_) {}

//! Destructor
/*!
 @note The destructor of class T should be public
 */
template <class T>
Hexagon2d<T>::~Hexagon2d() {}

//! Assignment operator
/*!
 @param rhs The hexagon to copy
 @note Class T should have an assignment operator
 */
template <class T>
Hexagon2d<T>& Hexagon2d<T>::operator=(const Hexagon2d& rhs) {
	if ( &rhs != this ) {
		center_ = rhs.center_;
		orientation_ = rhs.orientation_;
	}
	return *this;
}

//! Accessor to the center of the hexagon (read-write)
/*!
 @return A reference to the center of the hexagon
 */
template <class T>
Point2d<T>& Hexagon2d<T>::center() {
	return center_;
}

//! Accessor to the center of the hexagon (read only)
/*!
 @return A constant reference to the center of the hexagon
 */
template <class T>
const Point2d<T>& Hexagon2d<T>::center() const {
	return center_;
}

//! Accessor to the orientation vector (read-write)
/*!
 @return A reference to the orientation vector
 */
template <class T>
Vector2d<T>& Hexagon2d<T>::orientation() {
	return orientation_;
}

//! Accessor to the orientation vector (read only)
/*!
 @return A constant reference to the orientation vector
 */
template <class T>
const Vector2d<T>& Hexagon2d<T>::orientation() const {
	return orientation_;
}

//! Compute the location of the given point
/*!
 @return The given point
 */
template <class T>
Point2d<T> Hexagon2d<T>::operator[](Hexagon2D::Index index) const {
	// Get offset
	Angle<float> angle(0.0f);
	if ( index == Hexagon2D::Second ) angle = Angle<float>(60.0f,Angles::Deg);
	else if ( index == Hexagon2D::Third ) angle = Angle<float>(120.0f,Angles::Deg);
	else if ( index == Hexagon2D::Fourth ) angle = Angle<float>(180.0f,Angles::Deg);
	else if ( index == Hexagon2D::Fifth ) angle = Angle<float>(240.0f,Angles::Deg);
	else if ( index == Hexagon2D::Sixth ) angle = Angle<float>(300.0f,Angles::Deg);

	// Return point
	return Point2d<T>(
		center_.x() + orientation_.x() * cos(angle.rad()) - orientation_.y() * sin(angle.rad()),
		center_.y() + orientation_.x() * sin(angle.rad()) + orientation_.y() * cos(angle.rad())
	);
}

//! Test wether a point is inside the hexagon
/*!
 @param rhs The point to test for inclusion
 @param strict If set to true, a point located exactly on the perimeter of
			   the hexagon is not considered to belong to the hexagon
 @return True if the point is inside the hexagon, false otherwise
 */
template <class T>
bool Hexagon2d<T>::inside(const Point2d<T>& rhs, bool strict) const {
	// Get the six points
	Point2d<T> pts[] = {
		operator[](Hexagon2D::First),
		operator[](Hexagon2D::Second),
		operator[](Hexagon2D::Third),
		operator[](Hexagon2D::Fourth),
		operator[](Hexagon2D::Fifth),
		operator[](Hexagon2D::Sixth),
		operator[](Hexagon2D::First)
	};

	// Test point
	if ( strict == true ) {
		for ( int k = 0; k < 6; ++k ) {
			// Test point
			T area = Trgl2dFast<T>(pts[k],pts[k+1],rhs).area();
			if ( area <= 0 ) return false;
		}
	}
	else {
		for ( int k = 0; k < 6; ++k ) {
			// Test point
			T area = Trgl2dFast<T>(pts[k],pts[k+1],rhs).area();
			if ( area < 0 ) return false;
		}
	}
	return true;
}


//! Compute the intersection between the line and the hexagon
/*!
 @param rhs The line to intersect with the hexagon
 @param line_coordinate1 The parametric line coordinate of the first intersection between
                         the hexagon and the line, if both do intersect. To retrieve the
					     intersection point, call rhs.point(line_coordinate1)
 @param line_coordinate2 The parametric line coordinate of the second intersection between
                         the hexagon and the line, if both do intersect. To retrieve the
					     intersection point, call rhs.point(line_coordinate2)
 @param type If not nil, this parameter receives the type of intersection
             between the hexagon and the line. The intersection type
			 can be:<BR>
			 NoIntersection if the line does not intersect the hexagon,<BR>
			 Intersection if the line intersects the hexagon
 @return The number of intersections between the line and the hexagon
 */
template <class T>
int Hexagon2d<T>::intersection( const Line2d<T>& rhs, T& line_coordinate1, T& line_coordinate2, IntersectionType::Type* type) const
{
	// Get the six points
	Point2d<T> pts[] = {
		operator[](Hexagon2D::First),
		operator[](Hexagon2D::Second),
		operator[](Hexagon2D::Third),
		operator[](Hexagon2D::Fourth),
		operator[](Hexagon2D::Fifth),
		operator[](Hexagon2D::Sixth),
		operator[](Hexagon2D::First)
	};
	int res = 0;

	// Test hexagon points
	T tolerance = (T)0.000001;
	int index = -2;
	for ( int k = 0; k < 6; ++k ) {
		// Test if point falls on the line
		T coord = rhs.closest_point(pts[k]);
		Point2d<T> tmp = rhs.point(coord);
		if ( tmp.get_square_distance(pts[k]) >= tolerance ) continue;

		// Update line coordinates
		if ( res == 0 ) {
			line_coordinate1 = coord;
			index = k;
			++res;
			if ( type ) *type = IntersectionType::Intersection;
		}
		else {
			line_coordinate2 = coord;
			if ( type ) {
				if ( abs(k-index) <= 1 ) *type = IntersectionType::Parallel;
				else *type = IntersectionType::Intersection;
			}
			return 2;
		}
	}

	// Test hexagon lines
	for ( int k = 0; k < 6; ++k ) {
		// Test lines intersection
		Line2d<T> tmp_line(pts[k],pts[k+1]);
		T coord1, coord2;
		IntersectionType::Type tmp_type = rhs.intersection(tmp_line,coord1,&coord2);
		if ( tmp_type != IntersectionType::Intersection ) continue;
		if ( coord2 <= 0 ) continue;
		if ( coord2 >= 1 ) continue;

		// Update line coordinates
		if ( res == 0 ) {
			line_coordinate1 = coord1;
			index = k;
			++res;
			if ( type ) *type = IntersectionType::Intersection;
		}
		else {
			line_coordinate2 = coord1;
			if ( type ) *type = IntersectionType::Intersection;
			return 2;
		}
	}

	if ( type ) *type = ( ( res > 0 ) ? IntersectionType::Intersection : IntersectionType::NoIntersection );
	return res;
}

//! Compute the closest point along the hexagon
/*!
 @return The closest point along the hexagon
 @param rhs The point to project on the hexagon
 */
template <class T>
Point2d<T> Hexagon2d<T>::closest_point(const Point2d<T>& rhs) const {
	// Get the six points
	Point2d<T> pts[] = {
		operator[](Hexagon2D::First),
		operator[](Hexagon2D::Second),
		operator[](Hexagon2D::Third),
		operator[](Hexagon2D::Fourth),
		operator[](Hexagon2D::Fifth),
		operator[](Hexagon2D::Sixth),
		operator[](Hexagon2D::First)
	};

	// Find the two closest points
	int index1 = 0, index2 = 1;
	if ( rhs.get_square_distance(pts[0]) > rhs.get_square_distance(pts[1]) ) {
		index1 = 1;
		index2 = 0;
	}

	for ( int k = 2; k < 6; ++k ) {
		if ( rhs.get_square_distance(pts[index2]) <= rhs.get_square_distance(pts[k]) ) continue;
		if ( rhs.get_square_distance(pts[index1]) <= rhs.get_square_distance(pts[k]) ) index2 = k;
		else {
			index2 = index1;
			index1 = k;
		}
	}

	// Return point
	Line2d<T> tmp_line(pts[index1], pts[index2]);
	T param = tmp_line.closest_point(rhs);
	if ( param < 0 ) return tmp_line.point(0);
	if ( param > 1 ) return tmp_line.point(1);
	return tmp_line.point(param);
}

//! Compute the perimeter of the hexagon
/*!
 @return The perimeter of the hexagon
 */
template <class T>
T Hexagon2d<T>::perimeter() const {
	return (T)6*orientation_.length();
}

//! Compute the area of the hexagon
/*!
 @return The area of the hexagon
 */
template <class T>
T Hexagon2d<T>::area() const {
	return (T)3*sqrt((T)3)*orientation_.square_length() / (T)2;
}

//! Check if the hexagon is degenerate
/*!
 @return True if the hexagon is degenerate, false otherwise
 @note The hexagon is considered degenerate if its area is equal
       or below the tolerance threshold
 */
template <class T>
bool Hexagon2d<T>::degenerate(const T& tolerance) const {
	return ( area() <= tolerance );
}

#ifdef GAMEENGINE_NAMESPACES
} // namespace GameEngine
#endif

#endif
